package cc.eddic.pratice.programmingparadigms.nginxlog.impl;

import cc.eddic.pratice.programmingparadigms.advanced.EddicInterface;
import cc.eddic.pratice.programmingparadigms.nginxlog.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
@EddicInterface
public class LogReportGcsImpl implements LogReport {
    @Override
    public PageViewsAndUniqueVisitors pageViewsAndUniqueVisitors(List<NginxLogItem> items) {
        val uv = items.stream().map(NginxLogItem::getUniqueVisitorKey).distinct().count();
        return new PageViewsAndUniqueVisitors(items.size(), uv);
    }

    @Override
    public Map<Integer, PageViewsAndUniqueVisitors> pageViewsAndUniqueVisitorsByHour(List<NginxLogItem> items) {
        val pvMap = items.stream().map(HourModel::from).collect(Collectors.groupingBy(HourModel::getHour, Collectors.counting()));
        val uvMap = items.stream().map(HourModel::from)
                .collect(Collectors.groupingBy(HourModel::getHour,
                        Collectors.groupingBy(HourModel::getUniqueVisitorKey, Collectors.counting())));

        Map<Integer, PageViewsAndUniqueVisitors> result = new HashMap<>();
        for (int i = 0; i < 24; i++) {
            long pv = pvMap.getOrDefault(i, 0L);
            long uv = uvMap.containsKey(i) ? uvMap.get(i).size() : 0L;
            result.put(i, new PageViewsAndUniqueVisitors(pv, uv));
        }
        return result;
    }

    @Override
    public List<LogReport.PathModel> pageViewsAndUniqueVisitorsByPath(List<LogItem> items) {
        val pvMap = items.stream().map(PathModel::from).collect(Collectors.groupingBy(PathModel::getPath, Collectors.counting()));
        val uvMap = items.stream().map(PathModel::from)
                .collect(Collectors.groupingBy(PathModel::getPath,
                        Collectors.groupingBy(PathModel::getUniqueVisitorKey, Collectors.counting())));

        Map<String, PageViewsAndUniqueVisitors> result = new HashMap<>();
        pvMap.forEach((path, pv) -> {
            long uv = uvMap.containsKey(path) ? uvMap.get(path).size() : 0L;
            result.put(path, new PageViewsAndUniqueVisitors(pv, uv));
        });
        return result.entrySet().stream().map(e -> new LogReport.PathModel(e.getKey(), e.getValue().getPageViews(), e.getValue().getUniqueVisitors()))
                .sorted(Comparator.comparingLong(LogReport.PathModel::getPv).reversed())
                .collect(Collectors.toList());
    }


    @Getter
    @AllArgsConstructor
    private static class PathModel implements UniqueVisitors {
        private String ip;
        private String userAgent;
        private String path;

        @Override
        public String getUniqueVisitorKey() {
            return getIp() + "|||" + getUserAgent();
        }

        public static PathModel from(LogItem item) {
            return new PathModel(item.getIp(), item.getUserAgent(), item.getPath());
        }
    }

    @Getter
    @AllArgsConstructor
    private static class HourModel implements UniqueVisitors {
        private String ip;
        private String userAgent;
        private int hour;

        @Override
        public String getUniqueVisitorKey() {
            return getIp() + "|||" + getUserAgent();
        }

        public static HourModel from(NginxLogItem item) {
            int hour;
            val m = PATTERN.matcher(item.getTime());
            if (m.matches()) {
                try {
                    hour = Integer.parseInt(m.group("hour"));
                } catch (NumberFormatException e) {
                    log.error(e.getMessage(), e);
                    hour = -1;
                }
            } else {
                log.error("Error parsing time: {}", item.getTime());
                hour = -1;
            }
            return new HourModel(item.getIp(), item.getUserAgent(), hour);
        }
    }

    private final static Pattern PATTERN = Pattern.compile("\\d+/\\w+/\\d+:(?<hour>\\d+):\\d+:\\d+\\s+\\+\\d+");
}
